/*
    This source file is part of GeNa project.
    Copyright © GeNa project 2008
    For the latest info, see http://code.google.com/p/genaproject/ or  http://lumpyproject.forums-rpg.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Foobar is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. */

#include <stack>

#include "windows_manager.h"
#include "windows_factory.h"



//Find window
WindowsManager::WindowsManager(wxWindow* parent) : ParentWndEvtH(this)
{
    mRoot = new Node;
    mRoot->parentWnd = parent;
    mParent = parent;

    mSplitMode = false;
    //mSplitter = new Splitter(NULL);

    parent->PushEventHandler(&ParentWndEvtH);
}

WindowsManager::~WindowsManager()
{
    mParent->PopEventHandler();
    delete mRoot;
}

BEGIN_EVENT_TABLE(WindowsManager::ParentWindowEventHandler, wxEvtHandler)
    EVT_SIZE( WindowsManager::ParentWindowEventHandler::onSize)
END_EVENT_TABLE()

WindowsManager::Leaf* WindowsManager::getLeaf(wxPoint &p)
{
    Node* n = mRoot;
    if( n->leftLeaf)
        return n->leftLeaf;
    if( !n->leftNode)
        return NULL;
    n = n->leftNode;

    for(;;)
    {
        int c;

        if(n->mode == MODE_VERTICAL)
            c = p.x;
        else
            c = p.y;
        int middle = n->splitter->GetSashPosition();

        if( c < middle)
        {
            if( n->leftLeaf)
                return n->leftLeaf;
            n = n->leftNode;
        }
        else
        {
            if(n->mode == MODE_VERTICAL)
                p.x -= c;
            else
                p.y -= c;

            if(n->rightLeaf)
                return n->rightLeaf;
            n = n->leftNode;
        }
    }
}

WindowsManager::Node* WindowsManager::getNode(wxPoint &p, int depth)
{
    Node* n = mRoot;
    if( n->leftLeaf)
        return n;
    if( !n->leftNode)
        return NULL;
    if(depth == 1)
        return n;
    n = n->leftNode;

    for(int i=1;;i++)
    {
        if( i == depth)
            return n;

        int c;
        if(n->mode == MODE_VERTICAL)
            c = p.x;
        else
            c = p.y;
        int middle = n->splitter->GetSashPosition();

        if( c < middle)
        {
            if( n->leftLeaf)
                return n;
            n = n->leftNode;
        }
        else
        {
            if(n->mode == MODE_VERTICAL)
                p.x -= c;
            else
                p.y -= c;

            if(n->rightLeaf)
                return n;
            n = n->leftNode;
        }
    }
}

void WindowsManager::getWindow(wxPoint &p, int depth, Node* &node, Leaf* &leaf)
{
    node = NULL; leaf = NULL;

    Node* n = mRoot;
    if( n->leftLeaf)
    {
        node = n;
        return;
    }
    if( !n->leftNode)
        return;
    if(depth == 1)
    {
        node = n;
        return;
    }
    n = n->leftNode;

    for(int i=1;;i++)
    {
        if( i == depth)
        {
            node = n;
            return ;
        }

        int c;
        if(n->mode == MODE_VERTICAL)
            c = p.x;
        else
            c = p.y;
        int middle = n->splitter->GetSashPosition();

        if( c < middle)
        {
            if( n->leftLeaf)
            {
                leaf = n->leftLeaf;
                return;
            }
            n = n->leftNode;
        }
        else
        {
            if(n->mode == MODE_VERTICAL)
                p.x -= c;
            else
                p.y -= c;

            if(n->rightLeaf)
            {
                leaf = n->rightLeaf;
                return;
            }
            n = n->leftNode;
        }
    }
}


//Splitter control
void WindowsManager::setSplitterPosition(wxPoint p)
{
    mSplitterPoint = p;

    Leaf* l = getLeaf(p);
    if(l)
    {
        mSplitter->Hide();
        return;
    }

    if( mSplitter->GetParent() != l->wnd->getWindow())
    {
        mSplitter->Reparent(l->wnd->getWindow());
        mSplitter->SetSize(l->wnd->getWindow()->GetClientSize());
    }

    mSplitter->SetSashPosition( (mSplitter->GetSplitMode() == wxSPLIT_VERTICAL) ? p.x : p.y);
    mSplitter->Show();

    //TODO : add min size support
}

void WindowsManager::split()
{
    mSplitMode = false;

    if( !mSplitter->IsShown())
        return;

    Leaf* l = getLeaf(mSplitterPoint);
    Node* n = new Node;

    Node* parent = l->parent;
    if( parent->leftLeaf == l)
        parent->leftNode = n;
    else
        parent->rightNode = n;

    //Update nodes
    n->leftLeaf = l;
    l->parent = n;

    n->rightLeaf = new Leaf;
    n->rightLeaf->parent = n;

    //Update windows
    l->wnd->getWindow()->Reparent(mSplitter);
    n->rightLeaf->wnd = l->wnd->clone(mSplitter);

    n->splitter = mSplitter;
    mSplitter->mNode = n;
    mSplitter->Reparent(parent->splitter ? parent->splitter : mParent);


    updateLink(n);
    updateLink(parent);

    mSplitter = new Splitter(NULL);
}

void WindowsManager::onMouseMove(wxMouseEvent &event)
{
}

void WindowsManager::onMouseRightClick(wxMouseEvent &event)
{
}

void WindowsManager::onMouseLeftClick(wxMouseEvent &event)
{
}

void WindowsManager::onKeyUp(wxKeyEvent &event)
{
}

void WindowsManager::onFocusEvent(wxFocusEvent &event)
{
}

void WindowsManager::Node::updateLink()
{
    if( splitter)
    {
        if( splitter->IsSplit())
            splitter->Unsplit();

        wxWindow* w1;
        if( leftNode)
            w1 = leftNode->splitter;
        else if(leftLeaf)
            w1 = leftLeaf->wnd->getWindow();
        else
            w1 = NULL;

        wxWindow* w2;
        if( rightNode)
            w2 = rightNode->splitter;
        else if( rightLeaf)
            w2 = rightLeaf->wnd->getWindow();
        else
            w2 = NULL;

        if( !w1 || !w2)
            return;

        int pos = splitter->GetSashPosition();

        if( splitter->GetSplitMode() == wxSPLIT_VERTICAL)
            splitter->SplitVertically(w1,w2,pos);
        else
            splitter->SplitHorizontally(w1,w2,pos);

        splitter->UpdateSize();
    }
    else
    {
        if(leftLeaf)
        {
            leftLeaf->wnd->getWindow()->SetPosition(wxPoint(0,0));
            leftLeaf->wnd->getWindow()->SetSize(parentWnd->GetClientSize());
        }
        else if(leftNode)
        {
            leftNode->splitter->SetPosition(wxPoint(0,0));
            leftNode->splitter->SetSize(parentWnd->GetClientSize());
        }
    }
}

void WindowsManager::updateLink(Node* node)
{
    node->updateLink();
//    if( node->splitter)
//    {
//        if( node->splitter->IsSplit())
//            node->splitter->Unsplit();
//
//        wxWindow* w1;
//        if( node->leftNode)
//            w1 = node->leftNode->splitter;
//        else if(node->leftLeaf)
//            w1 = node->leftLeaf->wnd->getWindow();
//        else
//            w1 = NULL;
//
//        wxWindow* w2;
//        if( node->rightNode)
//            w2 = node->rightNode->splitter;
//        else if( node->rightLeaf)
//            w2 = node->rightLeaf->wnd->getWindow();
//        else
//            w2 = NULL;
//
//        int pos = node->splitter->GetSashPosition();
//
//        if( node->splitter->GetSplitMode() == wxSPLIT_VERTICAL)
//            node->splitter->SplitVertically(w1,w2,pos);
//        else
//            node->splitter->SplitHorizontally(w1,w2,pos);
//
//        node->splitter->UpdateSize();
//    }
//    else
//    {
//        if(node->leftLeaf)
//        {
//            node->leftLeaf->wnd->getWindow()->SetPosition(wxPoint(0,0));
//            node->leftLeaf->wnd->getWindow()->SetSize(mParent->GetClientSize());
//        }
//        else if(node->leftNode)
//        {
//            node->leftNode->splitter->SetPosition(wxPoint(0,0));
//            node->leftNode->splitter->SetSize(mParent->GetClientSize());
//        }
//    }
}

//Add window
void WindowsManager::addWindow(ManagedWindow* wnd, wxPoint p, int mode)
{
    Leaf* l = getLeaf(p);

    if(l)
    {
        mRoot->leftLeaf = new Leaf;
        mRoot->leftLeaf->parent = mRoot;
        mRoot->leftLeaf->wnd = wnd;
        wnd->getWindow()->SetPosition(wxPoint(0,0));
        wnd->getWindow()->SetSize(mParent->GetClientSize());
        wnd->getWindow()->Show();
        return;
    }

    Node* n = new Node;

    Node* parent = l->parent;
    if( parent->leftLeaf == l)
        parent->leftNode = n;
    else
        parent->rightNode = n;

    //Update nodes
    if( mode == MODE_TOP || mode == MODE_LEFT)
    {
        n->leftLeaf = l;
        l->parent = n;

        n->rightLeaf = new Leaf;
        n->rightLeaf->parent = n;
        n->rightLeaf->wnd = wnd;
    }
    else
    {
        n->rightLeaf = l;
        l->parent = n;

        n->leftLeaf = new Leaf;
        n->leftLeaf->parent = n;
        n->leftLeaf->wnd = wnd;
    }

    //Update windows
    Splitter* splitter = new Splitter(parent->splitter ? parent->splitter : mParent);
    n->splitter = splitter;

    l->wnd->getWindow()->Reparent(splitter);
    wnd->getWindow()->Reparent(splitter);

    updateLink(n);
    updateLink(parent);

    splitter->Show();
}



void WindowsManager::removeWindow(ManagedWindow* wnd)
{
    //TODO : remove a window from the WindowsManager
}

void WindowsManager::ReadConfCoroutine::start(WindowsManager* _mgr, wxXmlDocument &_file)
{
    mgr = _mgr;
    file = _file;
    //mgr->mParent->Hide();

    s.push(mgr->mRoot);

    wxXmlNode* xmlNode = file.GetRoot();

    if( !xmlNode)
        return;

    if( xmlNode->GetName() != wxT("RootWindow"))
    {
        std::cerr << "The layout root node must be the RootWindow." << std::endl;
        return;
    }

    if( xmlNode->GetNext())
        std::cerr << "The layout can't have multiple RootWindow, others RootWindow will be ignored." << std::endl;

    {
        wxXmlProperty* prop = xmlNode->GetProperties();
        wxSize size(800,600); wxPoint pos(30,30);
        while(prop)
        {
            if( prop->GetName() == wxT("Width"))
            {

                long int p;
                if( prop->GetValue().ToLong(&p,10))
                    size.x = p;
            }
            else if( prop->GetName() == wxT("Height"))
            {
                long int p;
                if( prop->GetValue().ToLong(&p,10))
                    size.y = p;
            }
            else if( prop->GetName() == wxT("PosX"))
            {

                long int p;
                if( prop->GetValue().ToLong(&p,10))
                    pos.x = p;
            }
            else if( prop->GetName() == wxT("PosY"))
            {
                long int p;
                if( prop->GetValue().ToLong(&p,10))
                    pos.y = p;
            }
            prop = prop->GetNext();
        }
        mgr->mParent->SetPosition(pos);
        mgr->mParent->SetSize(size);
        mgr->mParent->Update();

        ss.push( size);
    }

    xmlNode = xmlNode->GetChildren();
    if( !xmlNode)
        return;

    if( xmlNode->GetNext())
    {
        std::cerr << "The RootWindow can have only one children, only the first will be keep." << std::endl;
    }
    xs.push(xmlNode);
    waitingSplitter = NULL;
    Next();
}

void WindowsManager::ReadConfCoroutine::Next()
{
    if( waitingSplitter)
    {
        waitingSplitter->UpdateSize();
        waitingSplitter = NULL;

        Start(50,true);
        return;
    }

    if( !xs.size())
    {
        //mgr->mParent->Show();
        //mgr->mParent->SetSize(mgr->mParent->GetSize() + wxSize(1,1)); //FIXME : Hack so that wxWidgets refresh the window, do you have a better solution ?
        //mgr->mParent->SetSize(mgr->mParent->GetSize() - wxSize(1,1));

        delete this;
        return;
    }


    wxXmlNode* xmlNode = xs.top();
    Node* node = s.top();
    wxSize size = ss.top();
    s.pop(); xs.pop();

    if( xmlNode->GetName() == wxT("Splitter"))
    {
        if( node->splitter && node->splitter->IsSplit())
            node->splitter->Unsplit();


        Splitter* splitter = new Splitter(node->splitter ? node->splitter : mgr->mParent);
        splitter->SetSize(size);

        wxXmlProperty* prop = xmlNode->GetProperties();
        double f = 0.5f;
        long int pos = 0;
        while(prop)
        {
            if( prop->GetName() == wxT("Horizontal"))
            {
                if( prop->GetValue() == wxT("true"))
                    splitter->SetSplitMode(wxSPLIT_HORIZONTAL);
                else
                    splitter->SetSplitMode(wxSPLIT_VERTICAL);
            }
            else if( prop->GetName() == wxT("fPosition"))
            {
                prop->GetValue().ToDouble(&f);
            }
            else if( prop->GetName() == wxT("Position"))
            {
                prop->GetValue().ToLong(&pos,10);
            }
            prop = prop->GetNext();
        }

        Node* tmp = new Node;
        tmp->parent = node;
        if( node->leftNode || node->leftLeaf)
            node->rightNode = tmp;
        else
            node->leftNode = tmp;

        tmp->splitter = splitter;
        splitter->mNode = tmp;

        mgr->updateLink(node);

        wxSize size1(size), size2(size);
        if( splitter->GetSplitMode() == wxSPLIT_HORIZONTAL)
        {
            pos += f*(float)size.y;
            size1.y = pos;
            size2.y -= pos;
        }
        else
        {
            pos += f*(float)size.x;
            size1.x = pos;
            size2.x -= pos;
        }

        splitter->SetSashPosition(pos,true);



        wxXmlNode* xn = xmlNode->GetChildren();
        if( !xn)
        {
            std::cerr << "A splitter must have two subwindows (first window set of type UnknowWindow)." << std::endl;
            xs.push(new wxXmlNode(wxXML_ELEMENT_NODE,wxT("UnknowWindow")));
            s.push(tmp);
            ss.push(size2);
        }
        else
        {
            xs.push(xn);
            s.push(tmp);
            xn = xn->GetNext();
            ss.push(size2);
        }


        if( !xn)
        {
            std::cerr << "A splitter must have two subwindows (second window set of type UnknowWindow)." << std::endl;
            xs.push(new wxXmlNode(wxXML_ELEMENT_NODE,wxT("UnknowWindow")));
            s.push(node);
            ss.push(size1);
        }
        else
        {
            xs.push(xn);
            s.push(tmp);
            ss.push(size1);
        }

        if( xn->GetNext())
            std::cerr << "A splitter can't have more than two subwindows, others will be ignored." << std::endl;

        if( node->splitter)
            waitingSplitter = node->splitter;
    }
    else
    {
        if( node->splitter && node->splitter->IsSplit())
            node->splitter->Unsplit();

        ManagedWindow* wnd = WindowsFactoryInstance.createWindow<void>(std::string(xmlNode->GetName().mb_str()),node->splitter ? node->splitter : node->parentWnd, NULL);
        if( !wnd)
        {
            std::cerr << "Unknow Window : " << xmlNode->GetName();
            wnd = WindowsFactoryInstance.createWindow<void>("UnknowWindow",node->splitter ? node->splitter : mgr->mParent, NULL);
        }
        wnd->getWindow()->Show();

        Leaf* l = new Leaf;
        l->parent = node;
        if( node->leftNode || node->leftLeaf)
            node->rightLeaf = l;
        else
            node->leftLeaf = l;

        l->wnd = wnd;
        mgr->updateLink(node);
        wnd->getWindow()->SetSize(size);
    }


    Start(10,true);
}

BEGIN_EVENT_TABLE(WindowsManager::ReadConfCoroutine, wxTimer)
    EVT_SIZE( WindowsManager::ReadConfCoroutine::onSize)
END_EVENT_TABLE()

//Load/Save layout
bool WindowsManager::readConfigurationFromFile(wxXmlDocument &file)
{
    ReadConfCoroutine* coroutine = new ReadConfCoroutine;
    coroutine->start(this, file);
    return true;
}

bool WindowsManager::readConfigurationFromFile(const wxString &file)
{
    wxXmlDocument doc;
    if(!doc.Load(file))
        return false;
    return readConfigurationFromFile(doc);
}

void WindowsManager::writeConfigurationToFile(wxXmlDocument &file)
{
}

void WindowsManager::writeConfigurationToFile(const wxString &file)
{
}

